pub struct Board {
    pub data: Vec<Vec<u8>>,
}

impl Board {
    pub fn new(width: usize, height: usize) -> Self {
        Self {
            data: vec![vec![0; width]; height],
        }
    }

    pub fn width(&self) -> usize {
        self.data[0].len()
    }

    pub fn height(&self) -> usize {
        self.data.len()
    }

    pub fn check_full_lines(&self) -> Vec<usize> {
        self.data
            .iter()
            .enumerate()
            .filter(|(_, line)| line.iter().find(|v| **v == 0).is_none())
            .map(|(idx, _)| idx)
            .collect()
    }

    pub fn set(&mut self, x: i32, y: i32, val: u8) -> bool {
        if x < 0 || x >= self.width() as _ || y < 0 || y >= self.height() as _ {
            return false;
        }
        self.data[y as usize][x as usize] = val;
        true
    }

    pub fn has_block(&self, x: i32, y: i32) -> bool {
        if x < 0 || x >= self.width() as _ || y < 0 || y >= self.height() as _ {
            return false;
        }
        self.data[y as usize][x as usize] != 0
    }

    pub fn clear<I: IntoIterator<Item = usize>>(&mut self, lines: I) {
        let mut lines = lines.into_iter().collect::<Vec<_>>();
        lines.sort_by(|a, b| b.cmp(a));
        for line in &lines {
            for x in 0..self.width() {
                self.data[*line][x] = 0;
            }
        }

        for line in &lines {
            let line_data = self.data.remove(*line);
            self.data.push(line_data)
        }
    }

    pub fn visit<F: FnMut(usize, usize, u8)>(&self, mut visitor: F) {
        for y in 0..self.data.len() {
            for x in 0..self.data[y].len() {
                visitor(x, y, self.data[y][x])
            }
        }
    }

    pub fn visit_mut<F: FnMut(usize, usize, &mut u8)>(&mut self, mut visitor: F) {
        for y in 0..self.data.len() {
            for x in 0..self.data[y].len() {
                visitor(x, y, &mut self.data[y][x])
            }
        }
    }
}

#[cfg(test)]
mod test {
    use rand::{thread_rng, Rng};

    use crate::Board;

    #[test]
    fn board_clear() {
        let mut board = Board::new(8, 10);
        let mut rng = thread_rng();
        let mut heights = vec![];
        for _ in 0..board.width() {
            heights.push(rng.gen_range(3..(board.height() - 1)));
        }
        board.visit_mut(|x, y, val| {
            if y <= heights[x] {
                *val = 1
            }
        });

        println!("Before:");
        for y in (0..board.height()).rev() {
            for x in 0..board.width() {
                print!("{}", if board.data[y][x] == 0 { ' ' } else { '█' })
            }
            println!();
        }

        board.clear([2, 6]);
        println!("After:");
        for y in (0..board.height()).rev() {
            for x in 0..board.width() {
                print!("{}", if board.data[y][x] == 0 { ' ' } else { '█' })
            }
            println!();
        }
    }
}
